package net.xdclass.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import net.xdclass.constant.RedissonLockKey;
import net.xdclass.enums.BizCodeEnum;
import net.xdclass.enums.CouponCategoryEnum;
import net.xdclass.enums.CouponPublishEnum;
import net.xdclass.enums.CouponStateEnum;
import net.xdclass.exception.BizException;
import net.xdclass.interceptor.LoginInterceptor;
import net.xdclass.mapper.CouponRecordMapper;
import net.xdclass.model.CouponDO;
import net.xdclass.mapper.CouponMapper;
import net.xdclass.model.CouponRecordDO;
import net.xdclass.model.LoginUser;
import net.xdclass.request.NewUserCouponRequest;
import net.xdclass.service.CouponService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import net.xdclass.util.CommonUtil;
import net.xdclass.util.JsonData;
import net.xdclass.vo.CouponVo;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 孙浩
 * @since 2022-04-20
 */
@Service
@Slf4j
public class CouponServiceImpl extends ServiceImpl<CouponMapper, CouponDO> implements CouponService {

  @Autowired
  private CouponMapper couponMapper;

  @Autowired
  private CouponRecordMapper couponRecordMapper;

  @Autowired
  private RedissonClient redissonClient;

  @Cacheable(value = {"coupon"},key = "#root.methodName + #page+'_'+#size")
  @Override
  public Map<String, Object> pageCouponActivity(Integer page, Integer size) {
    Page<CouponDO> pageInfo = new Page<>(page, size);
    Page<CouponDO> couponDOPage = couponMapper.selectPage(pageInfo, new QueryWrapper<CouponDO>()
        .eq("publish", CouponPublishEnum.PUBLISH)
        .eq("category", CouponCategoryEnum.PROMOTION)
        .orderByDesc("create_time"));

    Map<String, Object> pageMap = new HashMap<>(3);
    /**
     * 总条数
     */
    pageMap.put("total_record", couponDOPage.getTotal());
    /**
     * 总页数
     */
    pageMap.put("total_page", couponDOPage.getPages());

    pageMap.put("current_data", couponDOPage.getRecords().stream().map(this::beanProcess).collect(Collectors.toList()));

    return pageMap;
  }

  private CouponVo beanProcess(CouponDO couponDO) {
    CouponVo couponVo = new CouponVo();
    BeanUtils.copyProperties(couponDO, couponVo);
    return couponVo;
  }

  @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
  @Override
  public void addCoupon(String couponId, String couponCategory) {
    LoginUser loginUser = LoginInterceptor.threadLocal.get();
    RLock lock = redissonClient.getLock(String.format(RedissonLockKey.lockCoupon, couponId,loginUser.getId()));
    //阻塞式等待，一个线程获取锁后，其他线程只能等待，和原生的方式循环调用不一样
    lock.lock();
    log.info("优惠券接口加锁成功{}", Thread.currentThread().getId());
    try {
      CouponDO couponDO = couponMapper.selectOne(new QueryWrapper<CouponDO>()
          .eq("id", couponId)
          .eq("publish", CouponPublishEnum.PUBLISH)
          .eq("category", couponCategory));
      this.checkCoupon(couponDO, loginUser.getId());
      CouponRecordDO couponRecordDO = new CouponRecordDO();
      BeanUtils.copyProperties(couponDO, couponRecordDO);
      couponRecordDO.setCreateTime(new Date());
      couponRecordDO.setUseState(CouponStateEnum.NEW.name());
      couponRecordDO.setUserId(loginUser.getId());
      couponRecordDO.setUserName(loginUser.getName());
      couponRecordDO.setCouponId(couponDO.getId());
      //高并发下扣减劵库存，采用乐观锁,当前stock做版本号,延伸多种防止超卖的问题,一次只能领取1张，TODO
      int rows = couponMapper.reduceStock(couponDO.getId(), couponDO.getStock());
      if (rows == 1) {
        //库存扣减成功才保存
        couponRecordMapper.insert(couponRecordDO);
      } else {
        log.warn("发放优惠券失败:{},用户:{}", couponDO, loginUser);
        throw new BizException(BizCodeEnum.COUPON_NO_STOCK);
      }
    } finally {
      log.info("解锁成功{}", Thread.currentThread().getId());
      lock.unlock();
    }
  }

  /**
   * 验证是否可以领取优惠券
   *
   * @param couponDO
   * @param id
   */
  private void checkCoupon(CouponDO couponDO, Long id) {
    if (couponDO == null) {
      throw new BizException(BizCodeEnum.COUPON_NO_EXITS);
    }

    //优惠券库存
    if (couponDO.getStock() <= 0) {
      throw new BizException(BizCodeEnum.COUPON_NO_STOCK);
    }

    //优惠券领取时间
    long time = CommonUtil.getCurrentTimestamp();

    long start = couponDO.getStartTime().getTime();

    long end = couponDO.getEndTime().getTime();

    if (time < start || time > end) {
      throw new BizException(BizCodeEnum.COUPON_OUT_OF_TIME);
    }

    //用户是否超过限制
    int recordNum = couponRecordMapper.selectCount(new QueryWrapper<CouponRecordDO>().eq("coupon_id", couponDO.getId()).eq("user_id", id));

    if (recordNum >= couponDO.getUserLimit()) {
      throw new BizException(BizCodeEnum.COUPON_OUT_OF_LIMIT);
    }

  }


  @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
  @Override
  public void initNewUserCoupon(NewUserCouponRequest newUserCouponRequest) {
    LoginUser loginUser = new LoginUser();
    loginUser.setId(newUserCouponRequest.getUserId());
    loginUser.setName(newUserCouponRequest.getName());
    LoginInterceptor.threadLocal.set(loginUser);
    List<CouponDO> newUserCouponList = couponMapper.selectList(new QueryWrapper<CouponDO>().eq("category", CouponCategoryEnum.NEW_USER));
    for (CouponDO couponDO : newUserCouponList) {
      this.addCoupon(couponDO.getId().toString(), CouponCategoryEnum.NEW_USER.name());
    }
  }
}
